<?php
// @formatter:off
/**
 * @file WCCollection.php
 * @author Alejandro Dario Simi
 * @date $Date: 2013-06-22 17:58:11 +0000 (Sat, 22 Jun 2013) $
 *
 * $Id: WCCollection.php 83 2013-06-22 17:58:11Z daemonraco@gmail.com $
 * $URL: http://wcomix.googlecode.com/svn/tags/wcomix-1.0/includes/representations/WCCollection.php $
 */
// @formatter:on

/**
 * @class WCCollection
 * This class represents entries saved in the table '%collections'.
 */
class WCCollection extends WCGTableRepresentation implements WCPreviews {
	/**
	 * @var string Special character to use as name's separator in collction's
	 * paths.
	 */
	public static $PathSeparator = "/";
	/**
	 * @var string Special character to use as separator in column
	 * col_previews.
	 */
	public static $PreviewsSeparator = "|";
	/**
	 * @var int This constant holds the maximum length of column
	 * col_previews.
	 */
	public static $PreviewsMaxLength = 640;
	/**
	 * @var string[] This variable holds the list of columns that cannot be
	 * directly modified by the pseudo-method __set().
	 * In this case col_id and col_group.
	 */
	protected $_PropertiesReadOnly = array(
		"id",
		"group"
	);
	/**
	 * @var string[] This variable holds the list of columns that must be
	 * treated as booleans and then send to the database as 'Y' and 'N' by
	 * the pseudo-method __set().
	 * In this case col_valid_preview and col_indexed.
	 */
	protected $_PropertiesBoolean = array(
		"valid_preview",
		"indexed"
	);
	/**
	 * @var string This is the prefix of every column on the table
	 * '%collections'.
	 */
	protected $_PropertiesPrefix = "col_";
	/**
	 * @var string This is the name of the represented table. In this case,
	 * '%collections'.
	 */
	protected $_PropertiesTable = "collections";
	/**
	 * @var string[] This variable holds the list of columns that may hold a
	 * null value. This is list is used by the pseudo-method __set() to
	 * perform the right operation when it's null.
	 * In this case col_previews.
	 */
	protected $_PropertiesNULL = array("previews");
	/**
	 * @var WCCollectionsHolder This is a pointer to the holder to which
	 * this collection representation is attached.
	 */
	protected $_holder = null;
	/**
	 * @var WCGContainer This container holds all comics of the collection.
	 */
	protected $_comics = null;
	/**
	 * @var WCGContainer This container holds all sub-collections.
	 */
	protected $_collections = null;
	/**
	 * @var string[string[string]] This is the list of collections identifiers
	 * of the branch where this collection is hanging. When it's false, it
	 * means it is not yet built.
	 */
	protected $_collectionPath = false;
	/**
	 * @var string This is similar to '$_collectionPath' but its only a
	 * string. When it's false, it means it is not yet built.
	 */
	protected $_path = false;
	/**
	 * Class basic constructor.
	 */
	public function __construct() {
		//
		// Calling the generic setting made by the inherited class.
		parent::__construct();
		//
		// Setting the holder pointer. It always use the collections
		// holder singleton.
		global $wcCollectionsHolder;
		$this->_holder = $wcCollectionsHolder;
	}
	/**
	 * Class basic destructor.
	 */
	public function __destruct() {
		//
		// This class has nothing to clean before deleting.
	}
	//
	// Public methods
	/**
	 * This method allows to add a sub-collection.
	 *
	 * @param WCCollection $collection Collection to be added.
	 * @return boolean Returns true if the sub-collection was successfully
	 * added.
	 */
	public function addCollection(WCCollection &$collection) {
		//
		// This is the default value to be returned.
		$out = false;
		//
		// Checking if the current collection and the one to be added
		// have no issues.
		if($this->ok() && $collection->ok()) {
			//
			// Increasing the children count on the current
			// collection.
			$this->subcount++;
			//
			// Setting this collection as parent for the one being
			// added.
			$collection->parent = $this->id;
			//
			// Because a new collection is been added, current
			// previews are no longer valid and they must be
			// regenerated when possible.
			$this->valid_preview = false;
			//
			// Ensuring containers existence.
			$this->createContainers();
			//
			// Adding collection to the list of sub-collections.
			$this->_collections->addId($collection->id());
			//
			// At this point, addition is considered as a success.
			$out = true;
		}
		//
		// Return the addition results.
		return $out;
	}
	/**
	 * This method allows to add a comic to the represented collection.
	 *
	 * @param WCComic $comic Comic to be added.
	 * @return boolean Returns true if the sub-collection was successfully
	 * added.
	 */
	public function addComic(WCComic &$comic) {
		//
		// This is the default value to be returned.
		$out = false;
		//
		// Checking if the current collection and the one to be added
		// have no issues.
		if($this->ok() && $comic->ok()) {
			//
			// Increasing the children count on the current
			// collection.
			$this->count++;
			//
			// Setting this collection as parent for the comic being
			// added.
			$comic->collection = $this->id;
			//
			// Because a new comic is been added, current
			// previews are no longer valid and they must be
			// regenerated when possible.
			$this->valid_preview = false;
			//
			// Ensuring containers existence.
			$this->createContainers();
			//
			// Adding comic to the list of comics.
			$this->_comics->addId($comic->id());
			//
			// At this point, addition is considered as a success.
			$out = true;
		}
		//
		// Return the addition results.
		return $out;
	}
	/**
	 * This recursive method creates a list of collections' descriptors
	 * representing a branch in the collections tree.
	 * Such lists is ordered from the top collection to the current one and
	 * each element contains name and id of each collection in the list.
	 *
	 * @return array Returns the list of collections on the branch from top
	 * to the current collection. If there's any issue, it returns false.
	 */
	public function collectionPath() {
		//
		// Checking if the path is not already built.
		if($this->_collectionPath === false) {
			//
			// Checking if current collection has no issues.
			if($this->ok()) {
				//
				// Creating the element to represent this
				// collection.
				$this->_collectionPath = array( array(
						"id" => $this->id,
						"name" => $this->name
					));
				//
				// Checking if this is a top collection.
				if($this->parent > 0) {
					//
					// Looking for the parent collection.
					$aux = $this->_holder->item($this->parent, $this->_group);
					//
					// Asking for its parent collection own
					// path.
					$this->_collectionPath = array_merge($aux->collectionPath(), $this->_collectionPath);
				}
			}
		}
		//
		// Returning collection's path.
		return $this->_collectionPath;
	}
	/**
	 * This method allows to get the list of sub-collections.
	 *
	 * @return WCCollection[] Returns a list of collections.
	 */
	public function collections() {
		//
		// Ensuring containers existence.
		$this->createContainers();
		//
		// Checking if the current collection has no issues and there's no
		// sub-collections list built
		if($this->ok()) {
			//
			// Preparing the statement to load all sub-collections.
			// @{
			$stmt = false;
			$stmtID = __CLASS__."::".__FUNCTION__;
			if($this->_db->has($stmtID)) {
				$stmt = $this->_db->get($stmtID);
			} else {
				$query = "select  col_id\n";
				$query .= "from    {$this->_dbprefix}collections\n";
				$query .= "where   col_group  = :group\n";
				$query .= " and    col_parent = :parent\n";

				$stmt = $this->_db->prepare($stmtID, $query);
			}
			// @}
			//
			// Asking for sub-collections.
			$params = array(
				":group" => $this->_group->id(),
				":parent" => $this->id
			);
			if($stmt->execute($params)) {
				//
				// Filling the list of sub-collections.
				foreach($stmt->fetchAll() as $row) {
					//
					// Adding a collection id to the list.
					$this->_collections->addId($row["col_id"]);
				}
			}
		}
		//
		// Returning the list of sub-collections.
		return $this->_collections->items();
	}
	/**
	 * This method is similar to 'collections()', but it returns all
	 * sub-collection until the leaves giving this way a list of all
	 * collections related to this node in the tree.
	 *
	 * @return WCCollection[] Returns a list of collections.
	 */
	public function collectionsFull() {
		//
		// Setting default value to be returned.
		$out = array();
		//
		// Getting all sub-collections and their children.
		foreach($this->collections() as $collection) {
			$out[] = $collection;
			foreach($collection->collectionsFull() as $child) {
				$out[] = $child;
			}
		}
		//
		// Returning results.
		return $out;
	}
	/**
	 * This method allows to get the list of comics having the current
	 * collection as a father.
	 *
	 * @return WCComics[] Returns a list of comics.
	 */
	public function comics() {
		//
		// Ensuring containers existence.
		$this->createContainers();
		//
		// Checking if the current collection has no issues.
		if($this->ok()) {
			//
			// Preparing the statement to load all collection's comics.
			// @{
			$stmt = false;
			$stmtID = __CLASS__."::".__FUNCTION__;
			if($this->_db->has($stmtID)) {
				$stmt = $this->_db->get($stmtID);
			} else {
				$query = "select  cmc_id\n";
				$query .= "from    {$this->_dbprefix}comics\n";
				$query .= "where   cmc_group      = :group\n";
				$query .= " and    cmc_collection = :collection\n";
				$query .= "order by cmc_collection_order,cmc_name asc\n";

				$stmt = $this->_db->prepare($stmtID, $query);
			}
			// @}
			//
			// Asking for comics.
			$params = array(
				":group" => $this->_group->id(),
				":collection" => $this->id
			);
			if($stmt->execute($params)) {
				//
				// Filling the list of comics.
				foreach($stmt->fetchAll() as $row) {
					//
					// Adding a comic id to the list.
					$this->_comics->addId($row["cmc_id"]);

				}
			}
		}
		//
		// Returning the list of comics.
		return $this->_comics->items();
	}
	/**
	 * This method creates a new random previews list based on collection's
	 * comics and sub-collections, and sets it in column col_previews.
	 *
	 * @return boolean Returns true if a previews list was generated.
	 */
	public function generatePreviews() {
		//
		// This is the value to return by default.
		$out = false;
		//
		// Loading global varibles.
		global $wcDefaults;
		//
		// Creating an empty list to hold all found previews.
		$previews = array();
		//
		// Loading the lists of comics and sub-collections and counting
		// the amount of items to check.
		// @{
		$comics = $this->comics();
		$collections = $this->collections();
		$comicCount = count($comics);
		$collectionCount = count($collections);
		// @}
		//
		// Because it is already counted, it is a good opportunity to keep
		// collection's stats updated.
		// @{
		$this->count = $comicCount;
		$this->subcount = $collectionCount;
		// @}
		//
		// If there are comics, it adds their previews to the list.
		if($comicCount > 0) {
			//
			// Adding comic's previews.
			foreach($comics as $comic) {
				$previews[] = $comic->previewUri();
			}
		}
		//
		// If there are sub-collections, it adds their previews to the
		// list.
		if($collectionCount > 0) {
			//
			// Adding sub-collection's previews list to the list.
			foreach($collections as $collection) {
				$previews = array_merge($previews, $collection->previews());
			}
		}
		//
		// Clearing the list of previews from any default previews.
		// Default previews is the image used where there's no preview.
		foreach($previews as $key => $value) {
			if($value == $wcDefaults['preview']) {
				unset($previews[$key]);
			}
		}
		//
		// Cropping the list to its ten first elements.
		$previews = array_slice($previews, 0, 10);
		//
		// Counting and checking that there was at least one preview.
		$count = count($previews);
		if($count) {
			//
			// Sticking all previews into a string and assigning it to
			// column col_previews.
			$this->previews = implode(self::$PreviewsSeparator, $previews);
			//
			// Collection's previews are set as valid.
			$this->valid_preview = true;
			//
			// Checking if this collection is not a top collection and
			// it has a parent.
			$parent = $this->parentCollection();
			if($parent) {
				//
				// Since this collection changed its previews and
				// collection's previews are also based on it's
				// sub-collections, parent preview is no longer
				// valid.
				$parent->valid_preview = false;
			}
			//
			// At this point, it is considered that a list of previews
			// was found and set to the current collection.
			$out = true;
		}
		//
		// Returning generation results.
		return $out;
	}
	/**
	 * This method checks if there is a preview for this collection.
	 *
	 * @return boolean Returns true when there's a preview for this
	 * collection.
	 */
	public function hasPreview() {
		//
		// It has the same result as hasPreviews().
		return $this->hasPreviews();
	}
	/**
	 * This method checks if there are previews for this collection.
	 *
	 * @return boolean Returns true when there are previews for this
	 * collection.
	 */
	public function hasPreviews() {
		//
		// When column col_previews has contents, it means there are
		// previews.
		return $this->previews ? true : false;
	}
	/**
	 * This method allows to get the current collection parent.
	 *
	 * @return WCCollection Returns a collection when it's not at the top,
	 * otherwise, returns null.
	 */
	public function parentCollection() {
		//
		// This is the default value to be returned.
		$out = null;
		//
		// Checking if the current collection is not a top one.
		if($this->parent) {
			//
			// Loading the collection's holder.
			global $wcCollectionsHolder;
			//
			// Getting the parent collection.
			$out = $wcCollectionsHolder->item($this->parent, $this->_group);
			//
			// If the parent couldn't be got or it has issues, no
			// collection is returned.
			if(!$out || !$out->ok()) {
				$out = null;
			}
		}
		// Returning the parent collection.
		return $out;
	}
	/**
	 * This method is similar to 'collectionPath()' but it only returns a
	 * simple string similar to a directories path, i.e. something like:
	 *	/parent-collection/collection
	 *
	 * @return string Returns a collection path.
	 */
	public function path() {
		//
		// Checking if the path is not already built.
		if($this->_path === false) {
			//
			// Setting a default value to be returned.
			$this->_path = "";
			//
			// Getting the current path.
			$path = $this->collectionPath();
			//
			// Checking that the current path is correct.
			if($path) {
				foreach($path as $subPath) {
					$this->_path .= self::$PathSeparator.$subPath["name"];
				}
			}
		}
		//
		// Returning collection's path.
		return $this->_path;

	}
	/**
	 * This method allows to get the thumbnail associated with the current
	 * collection.
	 *
	 * @return string Returns an image URIs.
	 */
	public function preview() {
		//
		// Checking if there are previews set.
		if($this->previews) {
			//
			// Getting the list of possible thumbnails.
			$list = $this->previews();
			//
			// Returning the first URI from the list.
			return array_shift($list);
		} else {
			//
			// Where there is no preview, it returns an empty string.
			return "";
		}
	}
	/**
	 * This method allows to get the list of thumbnails associated with the
	 * current collection.
	 *
	 * @return string[] Returns a list of image URIs.
	 */
	public function previews() {
		//
		// Checking if there are previews set.
		if($this->previews) {
			//
			// Splitting the contents of column col_previews and
			// returning them as an array.
			return explode(self::$PreviewsSeparator, $this->previews);
		} else {
			//
			// Where there are no previews, it returns an empty array.
			return array();
		}
	}
	/**
	 * This method removes a collection from the table. Internally calls to
	 * the inherited class remove() method, but before that, it clears all
	 * dependencies.
	 *
	 * @return boolean Returns the same value than WCGTableRepresentation::remove().
	 */
	public function remove() {
		//
		// Removing it as parent from all its comics.
		foreach($this->comics() as $comic) {
			//
			// Zero means no collection.
			$comic->collection = 0;
		}
		//
		// Removing it as parent from all its sub-collections.
		//
		// @note: this means that every sub-collection will become top
		// collections.
		foreach($this->collections() as $collection) {
			//
			// Zero means no collection.
			$collection->collection = 0;
		}
		//
		// Calling to the inherited method and returning its result.
		return parent::remove();
	}
	/**
	 * This method allows to remove a sub-collection.
	 *
	 * @param WCCollection $collection Sub-collection to be removed.
	 * @return boolean Returns true when the sub-collection was successfully
	 * removed from this collection.
	 */
	public function removeCollection(WCCollection &$collection) {
		//
		// This is the default value to be returned.
		$out = false;
		//
		// Checking that:
		//	- this collection has no issues.
		//	- the sub-collection to be removed has no issues.
		//	- and the sub-collection has this collection as parent.
		if($this->ok() && $collection->ok() && $collection->parent == $this->id) {
			//
			// Decreasing the sub-collections counter.
			$this->subcount--;
			//
			// Setting no parent to the sub-collection.
			//
			// @note: this means the sub-collection will become a top
			// one
			$collection->parent = 0;
			//
			// Setting this collection's previews as no valid.
			$this->valid_preview = false;
			//
			// Ensuring containers existence.
			$this->createContainers();
			//
			// Removing collection from the list of sub-collections.
			$this->_collections->removeId($collection->id());
			//
			// At this point, it is considered that it was removed.
			$out = true;
		}

		return $out;
	}
	/**
	 * This method allows to remove a comic from this collection.
	 *
	 * @param WCComic $comic Comic to be removed.
	 * @return boolean Returns true when the comic was successfully
	 * removed from this collection.
	 */
	public function removeComic(WCComic &$comic) {
		//
		// This is the default value to be returned.
		$out = false;
		//
		// Checking that:
		//	- this collection has no issues.
		//	- the comic to be removed has no issues.
		//	- and the comic belongs to this collection.
		if($this->ok() && $comic->ok() && $comic->collection == $this->id) {
			//
			// Decreasing the comics counter.
			$this->count--;
			//
			// Setting no collection to the comic.
			$comic->collection = 0;
			//
			// Setting this collection's previews as no valid.
			$this->valid_preview = false;
			//
			// Ensuring containers existence.
			$this->createContainers();
			//
			// Removing comic from the list of comics.
			$this->_comics->removeId($comic->id());
			//
			// At this point, it is considered that it was removed.
			$out = true;
		}

		return $out;
	}
	//
	// Protected methods
	/**
	 * The method creates all required containers.
	 */
	protected function createContainers() {
		//
		// Checking sub-collections container.
		if($this->_collections == null) {
			//
			// Loading global pointers.
			global $wcCollectionsHolder;
			//
			// Creating a list for sub-collections.
			$this->_collections = new WCGContainer($wcCollectionsHolder, $this->_group);
		}
		//
		// Checking comics container.
		if($this->_comics == null) {
			//
			// Loading global pointers.
			global $wcComicsHolder;
			//
			// Creating a list for comics.
			$this->_comics = new WCGContainer($wcComicsHolder, $this->_group);
		}
	}
	/**
	 * This is an internal method called by the pseudo-method __set()
	 *
	 * @param string $name Name of the parameter being modified.
	 * @param string $value Value to set to the property with name $name.
	 * @param string $realValue Value built by the pseudo-method __set().
	 * @return boolean Returns true when it is alright to modify the property.
	 */
	protected function subSet($name, &$value, &$realValue) {
		//
		// When the property being modified is the class name, any search
		// index on it is no longer valid and it should be updated ASAP.
		if($name == "name") {
			$this->indexed = false;
		}
		//
		// There are no conditions in this method that my interrupt the
		// property change.
		return true;
	}
	//
	// Public Class Methods
	/**
	 * This is a sorting method based on collection's order indicator. When
	 * the indicator has the same value, it uses NameSort().
	 * It can be used on usort and uasort functions.
	 *
	 * @param WCCollection $a Left operand.
	 * @param WCCollection $b Right operand.
	 * @return boolean Returns true when the order indicator of $a is greater
	 * than $b's.
	 */
	public static function Sort($a, $b) {
		$aorder = $a->order;
		$border = $b->order;
		return $aorder != $border ? $aorder > $border : self::NameSort($a, $b);
	}
	/**
	 * This is a sorting method based on collection id and it can be used on
	 * usort and uasort functions.
	 *
	 * @param WCCollection $a Left operand.
	 * @param WCCollection $b Right operand.
	 * @return boolean Returns true when the id of $a is greater than $b's.
	 */
	public static function IdSort($a, $b) {
		return $a->id > $b->id;
	}
	/**
	 * This is a sorting method based on collection names and it can be used
	 * on usort and uasort functions.
	 *
	 * @param WCCollection $a Left operand.
	 * @param WCCollection $b Right operand.
	 * @return boolean Returns true when the name of $a is greater than
	 * $b's.
	 */
	public static function NameSort($a, $b) {
		return $a->name > $b->name;
	}
	/**
	 * This is a sorting method based on collection paths and it can be used
	 * on usort and uasort functions.
	 *
	 * @param WCCollection $a Left operand.
	 * @param WCCollection $b Right operand.
	 * @return boolean Returns true when the path of $a is greater than
	 * $b's.
	 */
	public static function PathSort($a, $b) {
		return $a->path() > $b->path();
	}
}
?>